# -*- coding: utf-8 -*-

import math 
import os # 导入os模块
from functools import reduce #高阶函数使用

'''
这里根据https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000
教程进行基础章节内容均写入。
'''

#--------------Python基础------------------
#1、数据类型和变量
'''
请打印出以下变量的值：
n = 123
f = 456.789
s1 = 'Hello, world'
s2 = 'Hello, \'Adam\''
s3 = r'Hello, "Bart"'
'''
print("--------Python基础--------")
print("++++数据类型和变量++++")
n = 123
print('n = %d' % n)
f = 456.789
print('f = %s' % f)
s1 = 'Hello, world'
print(s1)
s2 = 'Hello, \\\'Adam\\\'\''
print(s2)
s3 = 'r\'Hello, "Bart"'
print(s3)
s4='''r\'\'\'Hello,
Lisa!\'\'\''''
print(s4)

#2、字符串和编码
#小明的成绩从去年的72分提升到了今年的85分，
#请计算小明成绩提升的百分点，并用字符串格式化显示出'xx.x%'，只保留小数点后1位：
print("++++字符串和编码++++")
old = 72
new = 85
r = (new - old) / 72 * 100
print('小明成绩提升%.1f%%' % r)

#3、使用list和tuple
'''
请用索引取出下面list的指定元素：

'''
print("++++list和tuple++++")
L = [
    ['Apple', 'Google', 'Microsoft'],
    ['Java', 'Python', 'Ruby', 'PHP'],
    ['Adam', 'Bart', 'Lisa']
]
# 打印Apple:
print(L[0][0])
# 打印Python:
print(L[1][1])
# 打印Lisa:
print(L[2][2])

#4、条件判断
print("++++条件判断++++")
'''
小明身高1.75，体重80.5kg。请根据BMI公式（体重除以身高的平方）帮小明计算他的BMI指数，并根据BMI指数：

低于18.5：过轻
18.5-25：正常
25-28：过重
28-32：肥胖
高于32：严重肥胖
用if-elif判断并打印结果：
'''
height = 1.75
weight = 80.5
bmi = weight / (height * height)
if bmi < 18.5:
	print("过轻")
elif bmi < 25:
	print("正常")
elif bmi < 28:
    print("过重")
elif bmi <= 32:
    print("肥胖")
else:
    print("严重肥胖")

#5、循环
print("++++循环++++")
#请利用循环依次对list中的每个名字打印出Hello, xxx!：
L = ['Bart', 'Lisa', 'Adam']
for x in L:
	print('Hello,%s!' % x)


print("--------函数--------")
print("++++函数++++")
#请利用Python内置的hex()函数把一个整数转换成十六进制表示的字符串：
n1 = 255
n2 = 1000
print('%d 转换成十六进制字符串 %s' % (n1, hex(n1)))
print('%d 转换成十六进制字符串 %s' % (n2, hex(n2)))

#注意缩进关系。空格跟tab不一样
def my_abs(x):
	if x >= 0:
		return x
	else:
		return -x

print(my_abs(-10))

'''

定义函数时，需要确定函数名和参数个数；

如果有必要，可以先对参数的数据类型做检查；

函数体内部可以用return随时返回函数结果；

函数执行完毕也没有return语句时，自动return None。

函数可以同时返回多个值，但其实就是一个tuple。
'''

'''
请定义一个函数quadratic(a, b, c)，接收3个参数，返回一元二次方程：

ax2 + bx + c = 0

的两个解。

提示：计算平方根可以调用math.sqrt()函数：
'''

def quadratic(a, b, c):
	# if not isinstance(a+b+c (int,float)):
	# 	 raise TypeError('bad operand type')
	s = b*b - 4 * a * c
	if s < 0:
		print("无最优解")
		return

	x1 = (-b + math.sqrt(s)) / (2 * a)
	x2 = (-b - math.sqrt(s)) / (2 * a)
	return x1 , x2


# 测试:
print('quadratic(2, 3, 1) =', quadratic(2, 3, 1))
print('quadratic(1, 3, -4) =', quadratic(1, 3, -4))

if quadratic(2, 3, 1) != (-0.5, -1.0):
    print('测试失败')
elif quadratic(1, 3, -4) != (1.0, -4.0):
    print('测试失败')
else:
    print('测试成功')

# 函数的参数
print("++++函数的参数++++")
print("顺序： 必选参数 > 默认参数 > 可变参数 > 命名关键字参数 > 关键字参数")
def hanshu_f1(a, b, c=0, *args, **kw): #必选 + 默认 + 可变 + 关键字
    pass
def hanshu_f2(a, b, c=0, *, d, **kw): #必选 + 默认 + 命名关键字 + 关键字
    pass


#必选参数
# 可接收一个或多个数并计算乘积：
def product(x, *args):
	if len(args) < 0:
		raise TypeError('输入错误')

	sum = x
	for num in args:
		if not isinstance(num,(int,float)):
			raise TypeError('参数有误')
		sum = sum * num
	return sum

# 测试
if product(5) != 5:
	print('product(5) =', product(5))
	print('测试失败!')
elif product(5, 6) != 30:
	print('product(5, 6) =', product(5, 6))
	print('测试失败!')
elif product(5, 6, 7) != 210:
	print('product(5, 6, 7) =', product(5, 6, 7))
	print('测试失败!')
elif product(5, 6, 7, 9) != 1890:
	print('product(5, 6, 7, 9) =', product(5, 6, 7, 9))
	print('测试失败!')
else:
    try:
        product()
        print('测试失败!')
    except TypeError:
        print('测试成功!')


print("--------高级特性--------")
print("++++切片++++")
def trim(s):#切片
	while s[:1] == " ":
		s = s[1:]
	while s[-1:] == " ":
		s = s[:-1]
	if s == " ":
		return ""
	return s
# 测试:
if trim('hello  ') != 'hello':
    print('01-测试失败!')
elif trim('  hello') != 'hello':
    print('02-测试失败!')
elif trim('  hello  ') != 'hello':
    print('03-测试失败!')
elif trim('  hello  world  ') != 'hello  world':
    print('04-测试失败!')
elif trim('') != '':
    print('05-测试失败!')
elif trim('    ') != '':
    print('06-测试失败!')
else:
    print('07-测试成功!')


print("++++迭代++++")
#请使用迭代查找一个list中最小和最大值，并返回一个tuple：
def findMinAndMax(L):
	if len(L) == 0:
		return None, None
	
	min = L[0]
	max = L[0]
	for index, value in enumerate(L):
		if not isinstance(value,(int ,float)):
			raise TypeError("参数错误")
		if value < min:
			min = value
		elif value > max:
			max = value
	return min,max
			

# 测试
if findMinAndMax([]) != (None, None):
    print('01-测试失败!')
elif findMinAndMax([7]) != (7, 7):
    print('02-试失败!')
elif findMinAndMax([7, 1]) != (1, 7):
    print('03-测试失败!')
elif findMinAndMax([7, 1, 3, 9, 5]) != (1, 9):
    print('04-测试失败!')
else:
    print('05-测试成功!')

print("++++列表生成式++++")
# print([d for d in os.listdir('.')])
#如果list中既包含字符串，又包含整数，由于非字符串类型没有lower()方法，所以列表生成式会报错.
#请修改列表生成式，通过添加if语句保证列表生成式能正确地执行：
List_L1 = ['Hello', 'World', 18, 'Apple', None]
# print([s.lower() for s in List_L1]) #将会报错
List_L2 = [s.lower() for s in List_L1 if isinstance(s,str)]
# 测试:
print(List_L2)
if List_L2 == ['hello', 'world', 'apple']:
    print('测试通过!')
else:
    print('测试失败!')

print("++++生成器 generator ++++")
print("一边循环一边计算的机制,循环的过程中不断推算出后续的元素，不必创建完整List")
#		    1
#          / \
#         1   1
#        / \ / \
#       1   2   1
#      / \ / \ / \
#     1   3   3   1
#    / \ / \ / \ / \
#   1   4   6   4   1
#  / \ / \ / \ / \ / \
# 1   5   10  10  5   1
# 把每一行看做一个list，试写一个generator，不断输出下一行的list：
def triangles():
	triangles_L1 = [1]
	while 1:
		yield triangles_L1
		triangles_L1 = [1] + [triangles_L1[i] + triangles_L1[i + 1] for i in range(len(triangles_L1) - 1)] + [1]
		
	

n = 0
results = []
for t in triangles():
    print(t)
    results.append(t)
    n = n + 1
    if n == 10:
        break
if results == [
    [1],
    [1, 1],
    [1, 2, 1],
    [1, 3, 3, 1],
    [1, 4, 6, 4, 1],
    [1, 5, 10, 10, 5, 1],
    [1, 6, 15, 20, 15, 6, 1],
    [1, 7, 21, 35, 35, 21, 7, 1],
    [1, 8, 28, 56, 70, 56, 28, 8, 1],
    [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
]:
    print('测试通过!')
else:
    print('测试失败!')

print("++++ 高阶函数 ++++")
# 绝对值相加
def addAbsFunc(x,y,func):
    return func(x) + func(y)

print(addAbsFunc(-10,5,abs))



def addReduceFuc(x,y):
    return x + y

print(reduce(addReduceFuc, [1,2,3,4,5,6]))

def mapFunc(x):
    return x * x

list_map_r = map(mapFunc, [1,2,3,4,5])
list_map_str = map(str, [1,3,4,5,6,7])
maplist = list(list_map_r)
print(maplist)
print(list_map_str)

'''
利用map()函数，把用户输入的不规范的英文名字，变为首字母大写，其他小写的规范名字。
输入：['adam', 'LISA', 'barT']，输出：['Adam', 'Lisa', 'Bart']：
'''
def normalize(name):
    print(name)
#   切片-> 高级特性
    return name[0].upper() + name[1:].lower()
            

# 测试:
L1 = ['adam', 'LISA', 'barT']
L2 = list(map(normalize, L1))
print(L2)

#Python提供的sum()函数可以接受一个list并求和，
#请编写一个prod()函数，可以接受一个list并利用reduce()求积：
def chengji(x,y):
    return x * y

def prod(L):
   return reduce(chengji, L)


print('3 * 5 * 7 * 9 =', prod([3, 5, 7, 9]))
if prod([3, 5, 7, 9]) == 945:
    print('测试成功!')
else:
    print('测试失败!')

# 利用map和reduce编写一个str2float函数，把字符串'123.456'转换成浮点数123.456：
def str2float(s):
    digits = {'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9}
    def fn(x, y):
        return x * 10 + y

    intNum,floatNum = s.split('.')
    intNum = reduce(fn, map(int, intNum))
    floatNum = 10 ** (-len(floatNum)) * reduce(fn, map(int, floatNum))
    return intNum + floatNum

print('str2float(\'123.456\') =', str2float('123.456'))
if abs(str2float('123.456') - 123.456) < 0.00001:
    print('测试成功!')
else:
    print('测试失败!')


#用filter求素数

#3开始的奇数序列
def  _odd_iter():
    n = 1
    while True:
        n = n + 2
        yield n #生成器
#筛选
def _not_divisible(n):
    return lambda x: x % n > 0

# 生成器，不断返回下一个素数：
def primes():
    yield 2
    it = _odd_iter()
    while True:
        n = next(it)
        yield n
        it = filter(_not_divisible(n), it)

# 打印1000以内的素数:
for n in primes():
    if n < 10:
        print(n)
    else:
        break

print("+++++++回文数 翻转后值一样+++++++")
def is_palindrome(n):
    # print(str(n), str(n)[::-1])
    return str(n) == str(n)[::-1]

# 测试:
output = filter(is_palindrome, range(1, 1000))
# print('1~1000:', list(output))
if list(filter(is_palindrome, range(1, 200))) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191]:
    print('测试成功!')
else:
    print('测试失败!')

print("+++++++排序+++++++")
# 按名字排序
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
def by_name(t):
   return t[0].lower()

L1 = sorted(L, key=by_name)
print(L1)

def by_scoure(t):
    return t[-1]
L2 = sorted(L, key=by_scoure, reverse = True)
print(L2)


print("---------返回函数---闭包--------")
def count():
    def f(j):
        def g():
            return j*j
        return g
    fs = []
    for i in range(1, 4):
        fs.append(f(i)) # f(i)立刻被执行，因此i的当前值被传入f()
    return fs
f1, f2, f3 = count()
print(f1(),f2(),f3())

# 利用闭包返回一个计数器函数，每次调用它返回递增整数：
# def createCounter():
#     i = 0
#     def counter():
#         nonlocal i
#         i = i + 1
#         return i
#     return counter
def createCounter():
    i = [0]
    def counter():
        i[0] = i[0] + 1
        return i[0]
    return counter


# 测试:
counterA = createCounter()
print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5
counterB = createCounter()
if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:
    print('测试通过!')
else:
    print('测试失败!')

print("+++++++匿名函数+++++++")
#改造
def is_odd(n):
    return n % 2 == 1

L = list(filter(is_odd, range(1, 20)))

L1 = list(filter(lambda x: x % 2 == 1, range(1,20)))
print(L,L1)

print('''+++++++装饰器++++++++
在函数调用前后自动打印日志，但又不希望修改now()函数的定义，
这种在代码运行期间动态增加功能的方式，称之为“装饰器”（Decorator）。''')


print('''
    ------- 模块 -----> 
     安装：1、 pip3 install Pillow
          2、 Anaconda 管理 类似iOS的pod管理
    ''')

print('''
---------面向对象----------
类：Class || 实例 Instance
自定义初始化方法:
def __init__(self, name, score):

私有变量(private)： 属性的名称前加上两个下划线__ 
self.__name = name
作用 隐藏内部实例变量 需对外提供set 与get 方法


特殊变量：__xxx__ 可以直接访问，不是private
举个🌰🌰
    ''')

class Student(object):
    def __init__(self, name, score):
        self.__name = name
        self.score = score

    def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >= 60:
            return "B"
        else:
            return "C"

    def print_scorce(self):
        print("名字：%s,分数：%s" % (self.__name, self.score))

xiaoming = Student("xiaoming", 96)
xiaoli = Student("xiaoli", 40)
xiaoming.score = 69
xiaoming.print_scorce()
print(xiaoming.get_grade())
xiaoli.print_scorce()
print(xiaoli.get_grade())

#练习题
print(''' 
    练习题：
    请把下面的Student对象的gender字段对外隐藏起来，
    用get_gender()和set_gender()代替，并检查参数有效性：
    ''')
class TestStudent(object):
    def __init__(self, name, gender):
        self.__name = name
        self.__gender = gender

    def set_name(self, name):
        if isinstance(name, str):
            self.__name = name
        else:
            print("请输入文字")

    def get_name(self):
        return self.__name;
    
    def set_gender(self, gender):
        if isinstance(gender, str):
            self.__gender = gender
        else:
            print("请输入文字")

    def  get_gender(self):
        return self.__gender

# 测试:
bart = TestStudent('Bart', 'male')
if bart.get_gender() != 'male':
    print('测试失败!')
else:
    bart.set_gender('female')
    if bart.get_gender() != 'female':
        print('测试失败!')
    else:
        print('测试成功!')


print('''
---------继承/多态/动态语言---------
继承可以把父类的所有功能都直接拿过来，这样就不必重零做起，子类只需要新增自己特有的方法，也可以把父类不适合的方法覆盖重写。

动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。

    ''')

print('''
--------获取对象信息--------
对象是什么类型、有哪些方法
1、type()
type(123) --->  <class 'int'>    
type(123)==type(456)  -->   True
type(123)==int   -->    True
type('abc')==str --->  True

2、推荐使用:  isinstance(h, str) 
可以将指定类型及其子类判断出 类似于oc中常用的isKindofClass

3、dir() 获得一个对象的所有属性和方法
 dir('ABC')
 ['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
    
 仅把属性和方法列出来是不够的，配合getattr()、setattr()以及hasattr()，我们可以直接操作一个对象的状态：
hasattr(obj, 'x') --> # 有属性'x'吗？ true or false
setattr(obj, 'y', 19) # 设置一个属性'y'

如果试图获取不存在的属性，会抛出AttributeError的错误：

getattr(obj, 'z', 404) # 获取属性'z'，如果不存在，返回默认值404

一个正确的用法的例子如下：

def readImage(fp):
    if hasattr(fp, 'read'):
        return readData(fp)
    return None

''')

# print(dir('ABC'))
print(hasattr(bart, "a"))
print(getattr(bart, 'a', 404))
setattr(bart, "a", 10)
print("a: %s" % getattr(bart, 'a', 404))

class Pro_Student(object):
    count = 0

    def __init__(self, name):
        self.name = name
        Pro_Student.count = self.count + 1

# 测试:
if Pro_Student.count != 0:
    print('测试失败!')
else:
    bart = Pro_Student('Bart')
    if Pro_Student.count != 1:
        print('测试失败!')
    else:
        lisa = Pro_Student('Bart')
        if Pro_Student.count != 2:
            print('测试失败!')
        else:
            print('Students:', Pro_Student.count)
            print('测试通过!')

print('''
---------面向对象高级编程---------
1. 动态绑定： 允许我们在程序运行的过程中动态给class加上功能
    ·给实例绑定一个属性：
    s = Student()
    s.name = 'Michael' # 动态给实例绑定一个属性

给实例绑定一个方法：
    def set_age(self, age): # 定义一个函数作为实例方法
         self.age = age

    s.set_age = MethodType(set_age, s) # 给实例绑定一个方法

给所有实例都绑定方法，可以给class绑定方法：
    def set_score(self, score):
         self.score = score
    Student.set_score = set_score

限制实例的属性方法：__slots__
比如，只允许对Student实例添加name和age属性。
class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

备注： 仅对当前类实例起作用，对继承的子类不起作用
    ''')

print('''
使用@property: 负责把一个方法变成属性调用的：
如只定义getter方法，不定义setter方法就是一个只读属性：
    ''')

class Property_Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

    @property 
    def name(self):
        return "只读属性name"
    

s = Property_Student()
s.score = 60 # OK，实际转化为s.set_score(60)
print(s.score) # OK，实际转化为s.get_score()
print(s.name)
# s.name = "小米" #这里会报错 只读属性

#练习 请利用@property给一个Screen对象加上width和height属性，以及一个只读属性resolution：
class Screen(object):
    @property
    def width(self):
        return self._width

    @width.setter
    def width(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        self._width = value

    @property
    def height(self):
        return self._height

    @height.setter
    def height(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        self._height = value

    @property
    def resolution(self):
        return 786432

# 测试:
s = Screen()
s.width = 1024
s.height = 768
print('resolution =', s.resolution)
if s.resolution == 786432:
    print('测试通过!')
else:
    print('测试失败!')


print('''
-----------多继承-----------
卧槽嘞，头一次使用多继承语言，想想还有点小激动呢
MixIn： 相对于OC、Swift又或者Java，Python中在设计类的继承关系时，
通常，主线都是单一继承下来的，例如，Ostrich继承自Bird。
但是，如果需要“混入”额外的功能，通过多重继承就可以实现，
比如，让Ostrich除了继承自Bird外，再同时继承Runnable。
这种设计通常称之为MixIn。
MixIn的目的就是给一个类增加多个功能，这样，在设计类的时候，
我们优先考虑通过多重继承来组合多个MixIn的功能，而不是设计多层次的复杂的继承关系。

吧啦吧啦都是概念，其实没有实际的理解
    ''')

print('''
-----------枚举-----------
   ''')

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)


#练习 把Student的gender属性改造为枚举类型，可以避免使用字符串：
class Gender(Enum):
    Male = 0
    Female = 1

class Enum_Student(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

# 测试:
bart = Enum_Student('Bart', Gender.Male)
if bart.gender == Gender.Male:
    print('测试通过!')
else:
    print('测试失败!')


print('''
---------错误处理---------
try
...

except
...

finally
...

Python内置的logging模块可以非常容易地记录错误信息：
 logging.exception(e)

    ''')
try:
    pass
except Exception as e:
    raise e
finally:
    pass

from functools import reduce

def str2num(s):
    try:
        return int(s)
    except Exception as e:
        return float(s)

def calc(exp):
    ss = exp.split('+')
    ns = map(str2num, ss)
    return reduce(lambda acc, x: acc + x, ns)

def main():
    r = calc('100 + 200 + 345')
    print('100 + 200 + 345 =', r)
    r = calc('99 + 88 + 7.6')
    print('99 + 88 + 7.6 =', r)

main()




print('''
-------调试方法--------
1、 print大法

2、 断言： assert OC: NSAssert()
凡是用print()来辅助查看的地方，都可以用断言（assert）来替代
assert n != 0, 'n is zero!'

Python解释器 -O参数来关闭assert
python -O err.py

3、logging 
print()替换为logging 输出一段文本 ，和assert比，logging不会抛出错误，而且可以输出到文件：

$ python err.py
INFO:root:n = 0
Traceback (most recent call last):
  File "err.py", line 8, in <module>
    print(10 / n)
ZeroDivisionError: division by zero
这就是logging的好处，它允许你指定记录信息的级别，有debug，info，warning，error等几个级别，当我们指定level=INFO时，logging.debug就不起作用了。同理，指定level=WARNING后，debug和info就不起作用了。这样一来，你可以放心地输出不同级别的信息，也不用删除，最后统一控制输出哪个级别的信息。

logging的另一个好处是通过简单的配置，一条语句可以同时输出到不同的地方，比如console和文件。


4、别扯没用的 直接IDE吧 断点调试
   * Visual Studio
   * PyCharm
    ''')

